package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"fmt"
	"gorm.io/gorm"
	"gorm_learn/conn"
	"gorm_learn/plugins"
	"gorm_learn/scopes"
	"gorm_learn/session"
	"log"
	"time"
)

// AccountInfo 结构体名对应表名
type AccountInfo struct {
	gorm.Model
	Name     string `gorm:"not null;unique"`
	Password string `gorm:"not null;"`
	Status   uint   `gorm:"default:0"`
}

/**
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果您已经为模型定义了指定的方法，它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误，GORM 将停止后续的操作并回滚事务。
*/
//func(*gorm.DB) error
func (acc *AccountInfo) AfterFind(tx *gorm.DB) error {
	if acc.UpdatedAt.IsZero() {

		acc.UpdatedAt = acc.CreatedAt
		fmt.Println("更新时间为null,已使用创建时间修复")
	}
	log.Println("###  执行了AfterFind！！###")
	return nil
}

//golang 对结构体进行格式化输出
/**
实现按字段打印，类似java的toString()
*/
func (acc *AccountInfo) String() string {
	b, err := json.Marshal(*acc)
	if err != nil {
		return fmt.Sprintf("%+v", *acc)
	}
	var out bytes.Buffer
	err = json.Indent(&out, b, "", "    ")
	if err != nil {
		return fmt.Sprintf("%+v", *acc)
	}
	return out.String()
}
func main() {

	///抽取到独立go文件中
	db := conn.GetDB()
	//test1(db)
	//test2(db)

	//named_args(db)
	//firstOrXXTest(db)
	//test_scope(db)
	//testHook(db)

	//访问接口即可查看插件数据   http://localhost:8080/metrics
	plugin(db)

	//relation.TestHasOne(db)
	session.TestSession(db)
	//fmt.Println("ok")

	////2. 使用map指定动态参数的值
	////这里参数使用map接收查询结果，但需要注意使用table()传入表名、或者model()传入空结构体指定表
	//var resultMaps []map[string]interface{}
	////db.Table("account_infos").Where("name like @name OR status = @errStatus ",map[string]interface{}{"name":"amy%","errStatus":"0"}).Find(&resultMaps)
	//db.Model(&AccountInfo{}).Where("name like @name OR status = @errStatus ", map[string]interface{}{"name": "amy%", "errStatus": "0"}).Find(&resultMaps)
	//for _, info := range resultMaps {
	//	fmt.Printf("info: %v\n", info)
	//}
}

func plugin(db *gorm.DB) {

	//	GORM provides Prometheus plugin to collect DBStats or user-defined metrics
	//  https://github.com/go-gorm/prometheus
	go plugins.Register()
	// 休眠
	time.Sleep(300 * time.Second)
}

/**
查询时可用的 hook
*/
func testHook(db *gorm.DB) {
	var invalidUser AccountInfo
	db.Where("updated_at IS NULL").First(&invalidUser)
	fmt.Println(invalidUser.String())
}

/**
Scopes
Scopes 允许你指定常用的查询，可以在调用方法时引用这些查询
*/
func test_scope(db *gorm.DB) {
	/**
	func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
	  return db.Where("amount > ?", 1000)
	}

	func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
	  return db.Where("pay_mode_sign = ?", "C")
	}

	func PaidWithCod(db *gorm.DB) *gorm.DB {
	  return db.Where("pay_mode_sign = ?", "C")
	}

	func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
	  return func (db *gorm.DB) *gorm.DB {
	    return db.Where("status IN (?)", status)
	  }
	}

	db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
	// 查找所有金额大于 1000 的信用卡订单

	db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
	// 查找所有金额大于 1000 的 COD 订单

	db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
	// 查找所有金额大于1000 的已付款或已发货订单
	*/
	var result []AccountInfo
	db.Scopes(scopes.ExistStatus([]int{4, 3}), scopes.TestedUsers).Find(&result)
	for _, info := range result {
		fmt.Printf("%v\n", info)
	}
}

/**在查询结果后，查无结果往往会继续进行一些操作，比如可以追加一些值后返回给前端，或者追加一些值后插入数据库
两个需求分别使用两个组合
Attrs+FirstOrInit 用于查无记录时初始化一个，未入库
Assign+FirstOrCreate 用于查无记录时，入库create
*/
func firstOrXXTest(db *gorm.DB) {
	// 按条件查找一个，若有结果，输出表数据
	//若查不到，则可以使用Attrs()函数继续为初始化的对象追加值，
	//注意：不管是否找到记录，Assign 都会将属性赋值给 viper_struct，但这些属性不会被用于生成查询 SQL，也不会被保存到数据库
	var user AccountInfo
	db.Where(AccountInfo{Name: "non_existing"}).
		//一种是使用键值对
		Attrs("updated_at", "N/A").Attrs("status", -1).
		//一种是使用struct指定初始化字段
		Attrs(AccountInfo{Password: "getPWD()"}).
		FirstOrInit(&user)

	fmt.Printf("查找或者初始化 名为non_existing用户： %s\n", user.String())
	// 按条件查找一个，若有结果，输出表数据
	//若查不到，则可以使用Attrs()函数继续为初始化的对象追加值，
	//注意：不管是否找到记录，Assign 都会将属性赋值给 viper_struct，但这些属性不会被用于生成查询 SQL，也不会被保存到数据库
	var newStatus uint8
	if user.Status == 3 {
		newStatus = 4
	} else {
		newStatus = 3
	}
	db.Where(AccountInfo{Name: "non_existing"}).Assign("status", newStatus).FirstOrCreate(&user)
	fmt.Printf("查找或者插入表 名为non_existing用户： %s\n", user.String())
}

//GORM 支持 sql.NamedArg 和 map[string]interface{}{} 形式的命名参数,使用@打头作为动态参数

func named_args(db *gorm.DB) {
	/*
		db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
		// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

		db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user)
		// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1

	*/

	//1. 使用sql.named(arg_name,value)指定动态参数的值
	var queryInfos []AccountInfo
	db.Where("name like @name OR status = @errStatus ", sql.Named("name", "amy%"), sql.Named("errStatus", 0)).Find(&queryInfos)
	fmt.Println("命名查询参数,结果行数：", len(queryInfos))
	for _, info := range queryInfos {
		fmt.Printf("info: %s\n", info.String())
	}

	//2. 使用map指定动态参数的值
	//这里参数使用map接收查询结果，但需要注意使用table()传入表名、或者model()传入空结构体指定表
	var resultMaps []map[string]interface{}
	//db.Table("account_infos").Where("name like @name OR status = @errStatus ",map[string]interface{}{"name":"amy%","errStatus":"0"}).Find(&resultMaps)
	db.Model(&AccountInfo{}).Where("name like @name OR status = @errStatus ", map[string]interface{}{"name": "amy%", "errStatus": "0"}).Find(&resultMaps)
	for _, info := range resultMaps {
		fmt.Printf("info: %#v\n", info)
	}

	//经测试发现字段=0传入不会被当成零值忽略。

}

func test1(db *gorm.DB) {

	// 自动迁移模式
	//db= db.AutoMigrate(&AccountInfo{})
	//fmt.Println("已创建表AccountInfo")

	//新增 Insert
	acc := AccountInfo{Name: "test_user6", Password: "2j33sdwe9."}
	acc2 := AccountInfo{Name: "test_user7", Password: "124fdws7z.", Status: 0}
	db.Create(&acc)
	db.Create(&acc2)

	//另一种写法
	db.Create(&AccountInfo{
		Name:     fmt.Sprintf("test_%d", time.Now().Nanosecond()),
		Password: "getPWD()",
		Status:   3,
	})

	//查找  LIKE
	var userInfos []AccountInfo
	db.Where("name LIKE ?", "%amy%").Find(&userInfos)
	for _, info := range userInfos {
		fmt.Printf("found 'amy?': name is %s,create at%s\n", info.Name, info.CreatedAt.Format("2006-01-02 15:04:05"))
	}

	//查找  时间比较
	fmt.Println("查找今天新增的记录")
	today := time.Now().Format("2006-01-02")
	t, _ := time.ParseInLocation("2006-01-02", today, time.Local)
	timeNumber := t.Unix()
	fmt.Println("timeNumber:", timeNumber)
	db.Where("created_at > ?", today).Find(&userInfos)
	for _, info := range userInfos {
		fmt.Printf("new records today: name is %s,create at%s\n", info.Name, info.CreatedAt.Format("2006-01-02 15:04:05"))
	}

	//查找 IN
	db.Where("name in (?)", []string{"amy4", "amy5"}).Find(&userInfos)
	for _, info := range userInfos {
		fmt.Printf("records in (amy4,amy5): name is %s,create at%s\n", info.Name, info.CreatedAt.Format("2006-01-02 15:04:05"))
	}

	//Where查询条件 (Struct & Map)
	var user AccountInfo

	/*
	      https://www.cnblogs.com/oxspirt/p/11240527.html
	      通过结构体变量更新字段值, gorm库会忽略零值字段。即 0, nil, "", false，这些值会被忽略掉，不会更新。
	   	如果想更新零值，可以使用map类型替代结构体。
	*/
	//Status:0 0是可选值之一，却会被当成零值，进而忽略Status字段的查询条件，这种情况只能使用map传入"status": 0来追加查询条件
	//db.Where(&AccountInfo{Name: "amy", Status: 3}).First(&user)
	db.Where(map[string]interface{}{"name": "amy", "status": 1}).First(&user)

	fmt.Println("the first (Status=1 and name='amy') record: ", user)

	//查找第一个记录： 状态不为0，姓名不为amy
	//NOT 以及使用结构体封装多个查询条件
	var user2 AccountInfo
	db.Not(AccountInfo{Name: "amy", Status: 1}).First(&user2)
	fmt.Println("the first NOT (Status=1 and name='amy') record: ", user2)

	//根据ID主键查询
	//1.数字型id
	db.Find(&userInfos, []int{1, 2, 3})
	for _, info := range userInfos {
		fmt.Printf("ID为（1,2,3）的用户信息: name is %s,create at%s\n", info.Name, info.CreatedAt.Format("2006-01-02 15:04:05"))
	}
	//2.uuid 【https://gorm.io/zh_CN/docs/query.html】
	//db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
	// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

	//
	// 获取全部记录
	var users []AccountInfo
	result := db.Find(&users)
	// SELECT * FROM accountInfos;
	r := result.RowsAffected // 返回找到的记录数，相当于 `len(users)`
	e := result.Error        // returns error
	fmt.Printf("所有用户数据有%d条 ，err: %v\n", r, e)

}

// AccountInfo_ 抽出业务所需少量字段组成新的VO
type AccountInfo_ struct {
	Name      string
	UpdatedAt time.Time
}

//String 封装 查询条件
func test2(db *gorm.DB) {
	/*String 条件
	// 获取第一条匹配的记录
	db.Where("name = ?", "jinzhu").First(&user)
	// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

	// 获取全部匹配的记录
	db.Where("name <> ?", "jinzhu").Find(&users)
	// SELECT * FROM users WHERE name <> 'jinzhu';

	// IN
	db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
	// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

	// LIKE
	db.Where("name LIKE ?", "%jin%").Find(&users)
	// SELECT * FROM users WHERE name LIKE '%jin%';

	// AND
	db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
	// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

	// Time
	db.Where("updated_at > ?", lastWeek).Find(&users)
	// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

	// BETWEEN
	db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
	// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
	*/
	var users []AccountInfo

	//写法1
	db.Select("name", "age").Find(&users)
	//SELECT name, age FROM users;

	db.Select("name", "status", "updated_at").Limit(5).Find(&users)
	fmt.Println("前5个账户信息1：")
	for i, user := range users {
		fmt.Printf("用户%d: %v\n", i+1, user.String())
	}

	//写法2  指定表字段（蛇形下划线写法）
	db.Select([]string{"name", "age"}).Find(&users)
	//SELECT name, age FROM users;

	var users2 []AccountInfo
	db.Select([]string{"name", "created_at"}).Limit(5).Find(&users2)
	fmt.Println("\n前5个账户信息2：")
	for i, user := range users2 {
		fmt.Printf("用户%d: %v\n", i, user.String())
	}

	//写法3  指定表字段（蛇形下划线写法）
	//db.Select([]string{"name", "age"}).Find(&users)
	//SELECT name, age FROM users;

	var users3 []AccountInfo_
	db.Table("account_infos").Select([]string{"name", "updated_at"}).Where("name like ?", "amy%").Limit(5).Scan(&users3)
	//db.Raw("SELECT * FROM account_infos WHERE name like ?","%_u%").Limit(5).Scan(&users3)
	fmt.Println("\n前5个账户信息3：")
	for i, v := range users3 {
		//gorm返回的update_at参数为空时对应的值是 0001-01-01T00:00:00Z
		if v.UpdatedAt.IsZero() {
			log.Fatalf("异常用户%d: %#v\n", i, v)
		}
		fmt.Printf("用户%d: %#v\n", i, v)
	}

}
